using System;
using System.Collections;
using System.Configuration;
using System.Text;
using System.Web.Profile;
using System.Web;

using Azke.Core.Util;
using Azke.Core.Service;
using Azke.Core.Domain;

namespace Azke.Core.Profile
{
    public class NHProfileProvider : ProfileProvider
    {

        #region Private Members

        private LogManager _logger = LogManager.GetLogger();
        private string _applicationName;
        private CoreRepository _coreRepository;

        #endregion

        #region Constructor
        // Summary:
        //     Creates an instance of the System.Web.Profile.SqlProfileProvider class.
        public NHProfileProvider()
        {

        }
        #endregion

        #region Initializor
        //
        // Summary:
        //     Initializes the SQL Server profile provider with the property values specified
        //     in the ASP.NET application's configuration file. This method is not intended
        //     to be used directly from your code.
        //
        // Parameters:
        //   config:
        //     A System.Collections.Specialized.NameValueCollection that contains the names
        //     and values of configuration options for the profile provider.
        //
        //   name:
        //     The name of the System.Web.Profile.SqlProfileProvider instance to initialize.
        //
        // Exceptions:
        //   System.Exception:
        //     The applicationName attribute value exceeds 256 characters.
        //
        //   System.ArgumentNullException:
        //     config is null.
        //
        //   System.Web.HttpException:
        //     The connectionStringName attribute is an empty string ("") or is not specified
        //     in the application configuration file for this System.Web.Profile.SqlProfileProvider
        //     instance.- or - The value of the connection string specified in the connectionStringName
        //     attribute value is empty or the specified connectionStringName value does
        //     not exist in the application configuration file for this System.Web.Profile.SqlProfileProvider
        //     instance.- or - The application configuration file for this System.Web.Profile.SqlProfileProvider
        //     instance contains an unrecognized attribute.
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            //
            // Initialize values from web.config.
            //

            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                name = "NHProfileProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "NHibernate Profile provider");
            }

            // Initialize the abstract base class.
            base.Initialize(name, config);

            if (config["applicationName"] == null || config["applicationName"].Trim() == "")
            {
                _applicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
            }
            else
            {
                _applicationName = config["applicationName"];
            }
            
            // NHibernate Session
            try
            {
                this._coreRepository = (CoreRepository)HttpContext.Current.Items["CoreRepository"];
                this._logger.Write(LogLevel.DEBUG, this.ToString(), "CoreRepository from HttpContext used.");
            }
            catch
            {
                this._coreRepository = new CoreRepository(true);
                this._logger.Write(LogLevel.WARN, this.ToString(), "No CoreRepository in HttpContext found, creating new CoreRepository.");
            }
        }
        #endregion

        #region Properties
        // Summary:
        //     Gets or sets the name of the application for which to store and retrieve
        //     profile information.
        //
        // Returns:
        //     The name of the application for which to store and retrieve profile information.
        //     The default is the System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath
        //     value.
        //
        // Exceptions:
        //   System.Web.HttpException:
        //     An attempt was made to set the System.Web.Profile.SqlProfileProvider.ApplicationName
        //     property by a caller that does not have System.Web.AspNetHostingPermissionLevel.High
        //     ASP.NET hosting permission.
        //
        //   System.Configuration.Provider.ProviderException:
        //     An attempt was made to set the System.Web.Profile.SqlProfileProvider.ApplicationName
        //     property to a string that is longer than 256 characters.
        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }
        #endregion

        #region Methods
        // Summary:
        //     Deletes user profile data for profiles in which the last activity date occurred
        //     before the specified date and time.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are deleted.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        // Returns:
        //     The number of profiles deleted from the data source.
        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            throw new NotImplementedException("Method are not implemented");
        }

        //
        // Summary:
        //     Deletes profile properties and information for the supplied list of profiles
        //     from the data source.
        //
        // Parameters:
        //   profiles:
        //     A System.Web.Profile.ProfileInfoCollection that contains profile information
        //     for profiles to be deleted.
        //
        // Returns:
        //     The number of profiles deleted from the data source.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     profiles has a System.Web.Profile.ProfileInfoCollection.Count value of zero.-
        //     or -One of the System.Web.Profile.ProfileInfo objects in profiles has a System.Web.Profile.ProfileInfo.UserName
        //     that is an empty string (""), exceeds a length of 256 characters, or contains
        //     a comma.
        //
        //   System.ArgumentNullException:
        //     profiles is null.- or -One of the System.Web.Profile.ProfileInfo objects
        //     in profiles has a System.Web.Profile.ProfileInfo.UserName that is null.
        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {
            throw new NotImplementedException("Method are not implemented");
        }

        //
        // Summary:
        //     Deletes profile properties and information from the data source for the supplied
        //     list of user names.
        //
        // Parameters:
        //   usernames:
        //     A string array of user names for profiles to be deleted.
        //
        // Returns:
        //     The number of profiles deleted from the data source.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     The length of usernames is zero.- or -One of the items in usernames is an
        //     empty string (""), exceeds a length of 256 characters, or contains a comma.-
        //     or -Two or more items in usernames have the same value.
        //
        //   System.ArgumentNullException:
        //     usernames is null.- or -One of the items in usernames is null.
        public override int DeleteProfiles(string[] usernames)
        {
            int i = 0;
            if (usernames != null || usernames.Length < 1)
            {               
                try
                {
                    foreach (string username in usernames)
                    {
                        if (!String.IsNullOrEmpty(username))
                        {
                            IList profiles = this._coreRepository.GetProfileByUserName(username);
                            if (profiles != null && profiles.Count > 0)
                            {
                                foreach (UserProfile profile in profiles)
                                {
                                    this._coreRepository.DeleteObject(profile);
                                    i++;
                                }
                            }
                        }
                        else
                            throw new ArgumentException("Cannot delete profile, UserName is null or empty.");
                    }
                }
                catch (Exception ex)
                {
                    _logger.Write(LogLevel.ERROR, this.ToString(), "An error occured while deleting profile data.", ex);
                    throw new System.Configuration.Provider.ProviderException("An error occured while deleting profile data.");
                }
            }
            else
                throw new ArgumentException("Cannot delete profile, UserNames is null or empty.");

            return i;
        }

        //
        // Summary:
        //     Retrieves profile information for profiles in which the last activity date
        //     occurred on or before the specified date and time and the user name for the
        //     profile matches the specified name.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        //   totalRecords:
        //     When this method returns, contains an integer that identifies the total number
        //     of profiles. This parameter is passed uninitialized.
        //
        //   pageIndex:
        //     The index of the page of results to return. pageIndex is zero-based.
        //
        //   usernameToMatch:
        //     The user name for which to search.
        //
        //   pageSize:
        //     The size of the page of results to return.
        //
        // Returns:
        //     A System.Web.Profile.ProfileInfoCollection containing user profile information
        //     for inactive profiles where the user name matches the supplied usernameToMatch
        //     parameter.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     usernameToMatch is null.
        //
        //   System.ArgumentException:
        //     usernameToMatch is an empty string ("") or exceeds 256 characters.- or -pageIndex
        //     is less than zero.- or -pageSize is less than one.- or -pageIndex multiplied
        //     by pageSize is larger than System.Int32.MaxValue.
        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException("Method are not implemented");
        }

        //
        // Summary:
        //     Retrieves profile information for profiles in which the user name matches
        //     the specified name.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   totalRecords:
        //     When this method returns, contains an integer that identifies the total number
        //     of profiles. This parameter is passed uninitialized.
        //
        //   pageIndex:
        //     The index of the page of results to return. pageIndex is zero-based.
        //
        //   usernameToMatch:
        //     The user name for which to search.
        //
        //   pageSize:
        //     The size of the page of results to return.
        //
        // Returns:
        //     A System.Web.Profile.ProfileInfoCollection containing user profile information
        //     for profiles where the user name matches the supplied usernameToMatch parameter.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     usernameToMatch is null.
        //
        //   System.ArgumentException:
        //     usernameToMatch is an empty string ("") or exceeds 256 characters.- or -pageIndex
        //     is less than zero.- or -pageSize is less than one.- or -pageIndex multiplied
        //     by pageSize is larger than System.Int32.MaxValue.
        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException("Method are not implemented");
        }

        //
        // Summary:
        //     Retrieves user profile data for profiles in which the last activity date
        //     occurred on or before the specified date and time.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        //   totalRecords:
        //     When this method returns, contains an integer that identifies the total number
        //     of profiles. This parameter is passed uninitialized.
        //
        //   pageIndex:
        //     The index of the page of results to return. pageIndex is zero-based.
        //
        //   pageSize:
        //     The size of the page of results to return.
        //
        // Returns:
        //     A System.Web.Profile.ProfileInfoCollection containing user profile information
        //     about the inactive profiles.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     pageIndex is less than zero.- or -pageSize is less than one.- or -pageIndex
        //     multiplied by pageSize is larger than System.Int32.MaxValue.
        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException("Method are not implemented");
        }


        //
        // Summary:
        //     Retrieves user profile data for profiles in the data source.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   totalRecords:
        //     When this method returns, contains an integer that identifies the total number
        //     of profiles. This parameter is passed uninitialized.
        //
        //   pageIndex:
        //     The index of the page of results to return. pageIndex is zero-based.
        //
        //   pageSize:
        //     The size of the page of results to return.
        //
        // Returns:
        //     A System.Web.Profile.ProfileInfoCollection containing user profile information
        //     for all of the profiles in the data source.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     pageIndex is less than zero.- or -pageSize is less than one.- or -pageIndex
        //     multiplied by pageSize is larger than System.Int32.MaxValue.
        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException("Method are not implemented");
        }

        //
        // Summary:
        //     Gets the number of profiles in the data source where the last activity date
        //     occurred on or before the specified userInactiveSinceDate.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        // Returns:
        //     The number of profiles in the data source for which the last activity date
        //     occurred before the specified date and time.
        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            throw new NotImplementedException("Method are not implemented");
        }

        //
        // Summary:
        //     Retrieves profile property information and values from a SQL Server profile
        //     database.
        //
        // Parameters:
        //   properties:
        //     A System.Configuration.SettingsPropertyCollection containing profile information
        //     for the properties to be retrieved.
        //
        //   sc:
        //     The System.Configuration.SettingsContext that contains user profile information.
        //
        // Returns:
        //     A System.Configuration.SettingsPropertyValueCollection containing profile
        //     property information and values.
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext sc, SettingsPropertyCollection properties)
        {
            // Create the Collection to return
            SettingsPropertyValueCollection returnCollection =
                                      new SettingsPropertyValueCollection();

            // Check if there are properties to retrieve
            if (properties == null || properties.Count < 1 || sc == null)
                return returnCollection;
            
            // Get the user from the Context
            string username = (string)sc["UserName"];

            // Load the profile
            if (String.IsNullOrEmpty(username))
            {


                // Go through each property to determine how to store/retrieve it
                foreach (SettingsProperty property in properties)
                {
                    // If it's Provider specific, try and see if it can be  
                    // Stored as a string
                    if (property.SerializeAs == SettingsSerializeAs.ProviderSpecific)
                    {
                        // If it is a primitive type or a string, then just store as string
                        if (property.PropertyType.IsPrimitive ||
                            (property.PropertyType == typeof(string)))
                        {
                            property.SerializeAs = SettingsSerializeAs.String;
                        }
                        else // Else serialize it as XML
                        {
                            property.SerializeAs = SettingsSerializeAs.Xml;
                        }
                    }
                    // Set it as use default to be overridden if the value as been found
                    SettingsPropertyValue propValue = new SettingsPropertyValue(property);
                    propValue.IsDirty = false;

                    // Add it to the collection to return
                    returnCollection.Add(propValue);
                }
                return returnCollection;
            }

            try
            {
                IList userProfile = this._coreRepository.GetProfileByUserName(username);

                if (userProfile != null)
                {
                    // Go through each property to determine how to store/retrieve it
                    foreach (SettingsProperty property in properties)
                    {
                        // Create a SettingsPropertyValue
                        SettingsPropertyValue setting = new SettingsPropertyValue(property);

                        // Read a persisted property value from the data source
                        foreach (UserProfile userProperty in userProfile)
                        {
                            if (property.Name == userProperty.PropertyName)
                            {
                                object val = userProperty.PropertyValueString;
                                setting.PropertyValue = val;
                                setting.Deserialized = true;
                                setting.IsDirty = true;

                                // Update and save ActivityDate
                                userProperty.LastActivityDate = DateTime.Now;
                                this._coreRepository.SaveObject(userProperty);

                                // If val is null, set the property value to null
                                /*if (val == null)
                                {
                                    setting.PropertyValue = null;
                                    setting.Deserialized = true;
                                    setting.IsDirty = false;
                                }
                                // If val is not null, set the property value to a non-null value
                                else
                                {
                                    setting.PropertyValue = val;
                                    setting.Deserialized = true;
                                    setting.IsDirty = true;
                                    // TODO: Set pp.PropertyValue to the property value and
                                    // pp.Deserialized to true, or set pp.SerializedValue to
                                    // the serialized property value and Deserialized to false.
                                    // Which strategy you choose depends on which was written
                                    // to the data source: PropertyValue or SerializedValue.
                                }*/

                                // Add the SettingsPropertyValue to the collection
                                returnCollection.Add(setting);
                            }
                        }
                    }
                    return returnCollection;
                }
                else
                {
                    // Nothing was stored in the database, return a new collection
                    foreach (SettingsProperty property in properties)
                    {
                        returnCollection.Add(new SettingsPropertyValue(property));
                    }
                    return returnCollection;
                }
                
            }
            catch (Exception ex)
            {
                _logger.Write(LogLevel.ERROR, this.ToString(), "An error occured while trying to save profile data to database.", ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to save profile data to database.");
            }
        }

        //
        // Summary:
        //     Updates the NHibernate profile database with the specified property values.
        //
        // Parameters:
        //   properties:
        //     A System.Configuration.SettingsPropertyValueCollection containing profile
        //     information and values for the properties to be updated.
        //
        //   sc:
        //     The System.Configuration.SettingsContext that contains user profile information.
        public override void SetPropertyValues(SettingsContext sc, SettingsPropertyValueCollection properties)
        {
            // Check if there are properties to retrieve
            if (properties == null || properties.Count < 1 || sc == null)
                return;
            
            // Determine If Anonymous
            HttpContext ctx = HttpContext.Current;
            bool isAnonymous = true;
            if (HttpContext.Current != null &&
                HttpContext.Current.Request != null &&
                HttpContext.Current.Request.IsAuthenticated)
            {
                isAnonymous = false;
            }
            if (isAnonymous)
                return;
            // Get the user from the Context
            string username = (string)sc["UserName"];
            
            // Load the profile
            if (String.IsNullOrEmpty(username))
                return;

            try
            {
                IList userProfile = this._coreRepository.GetProfileByUserName(username);

                if (userProfile != null)
                {
                    // Go through each value to find the ones that need updating
                    foreach (SettingsPropertyValue value in properties)
                    {
                        // Only save if necessary
                        if (value.IsDirty)
                        {
                            foreach (UserProfile profileValue in userProfile)
                            {
                                if (profileValue.PropertyName == value.Property.Name)
                                {
                                    profileValue.PropertyValueString = (string)value.PropertyValue;
                                    /*if (value.Deserialized)
                                    {
                                        profileValue.PropertyValueString = (string)value.PropertyValue;
                                        // TODO: Write property.PropertyValue to the data source
                                    }
                                    else
                                    {
                                        profileValue.PropertyValueString = (string)value.SerializedValue;
                                        // TODO: Write property.SerializedValue to the data source
                                    }*/
                                    profileValue.LastUpdatedDate = DateTime.Now;
                                    this._coreRepository.UpdateObject(profileValue);
                                }
                            }
                        }
                    }
                }
                else
                {
                    // Nothing found in database, create new settings
                    foreach (SettingsPropertyValue value in properties)
                    {
                        UserProfile valueToSave = new UserProfile();
                        valueToSave.UserName = (string)username;
                        valueToSave.PropertyName = (string)value.Property.Name;
                        valueToSave.PropertyValueString = (string)value.PropertyValue;
                        valueToSave.LastUpdatedDate = DateTime.Now;
                        valueToSave.LastActivityDate = DateTime.Now;
                        this._coreRepository.SaveObject(valueToSave);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Write(LogLevel.ERROR, this.ToString(), "An error occured while trying to save profile data to database.", ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to save profile data to database.");
            }
            
        }

        #endregion
    }
}
